// Copyright (C) 2006 Taylor L. Riché <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

#include "PhysInterface.hh"

int PhysInterface::procNum = 0;

PhysInterface::PhysInterface(std::string intf) : fromSet(false), intfName(intf), sysReady(false)
{
  coreSocket = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
  struct ifreq myReq;
  if(coreSocket == -1) {
    std::cerr << "Problem with opening socket on device " << intf << std::endl;
    exit(1);
  }
  else {
    memset((void *)&myReq, '\0', sizeof(struct ifreq));
    memcpy(myReq.ifr_name, intf.c_str(), sizeof(intf.c_str()));
    if(ioctl(coreSocket, SIOCGIFINDEX, &myReq) < 0) {
      std::cerr << "Problem with ioctl: " << strerror(errno) << std::endl;
      exit(1);
    }
    intfNum = myReq.ifr_ifindex;
    struct sockaddr_ll myOpts;
    myOpts.sll_family = AF_PACKET;
    myOpts.sll_protocol = htons(ETH_P_ALL);
    myOpts.sll_ifindex = intfNum;
    unsigned int bindReturn = bind(coreSocket, (struct sockaddr *)&myOpts,
				   sizeof(struct sockaddr_ll));
    if(bindReturn != 0) {
      close(coreSocket);
      std::cerr << "Problem with binding: " << strerror(bindReturn) << std::endl;
      exit(1);
    }
  }
  socklen_t namelen = sizeof(coreFrom);
  memset(&coreFrom, '\0', namelen);
  coreFrom.sll_family = AF_PACKET;
  coreFrom.sll_ifindex = intfNum;
  if(ioctl(coreSocket, SIOCGIFHWADDR, &myReq) < 0) {
    std::cerr << "Problem with ioctl: " << strerror(errno) << std::endl;
    exit(1);
  }
  //  memcpy(coreFrom.sll_addr, myReq.ifr_hwaddr.sa_data, 6);
  // Set broadcast address for destination MAC address
  for(int i = 0; i < 6; ++i) {
    coreFrom.sll_addr[i] = 0xff;
  }
  coreFrom.sll_halen = 6;
  coreFrom.sll_hatype = coreFrom.sll_pkttype = coreFrom.sll_protocol = 0;
  {
    Synchronized::Scope mutex(this);
    fromSet = true;
    notifyAll();
  }
  //  MethodWrapper<PhysInterface> * mw = new MethodWrapper<PhysInterface>;
  //  mw->object = this;
  //  mw->method = &PhysInterface::process;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  if(pthread_create(&coreThread, &attr, startNIC,(void*) this)) {
    std::cerr << "pthread_create failed" << std::endl;
    std::exit(1);
  }
  cpu_set_t set;
  CPU_ZERO(&set);
  CPU_SET(procNum++, &set);
  std::cout << "Setting affinity of thread " << coreThread << " to proc " << procNum - 1 << "." << std::endl;
  if(pthread_setaffinity_np(coreThread, sizeof(set), &set) != 0) {
    std::cerr << "Affinity not set: " << strerror(errno) << std::endl;
  }

}

void
PhysInterface::process()
{
   struct sockaddr_ll from;
   {
     Synchronized::Scope mutex(this);
     while(!sysReady) {
       wait();
     }
   }
   while(true) {
     unsigned char * newpkt = new unsigned char[BUFSIZE];
     ssize_t pktSize;
     socklen_t namelen = sizeof(from);
     pktSize = recvfrom(coreSocket, newpkt, BUFSIZE, 0, (struct sockaddr *)&from, &namelen);
     if(pktSize < 0) {
       std::cerr << "Problem with receiving packet on device " << intfName << std::endl;
       std::cerr << errno << ": " << strerror(errno) << std::endl;
       delete[] newpkt;
       exit(1);
     }
     else if(from.sll_pkttype != PACKET_OUTGOING && from.sll_pkttype != PACKET_BROADCAST) {
       std::pair<lagniappe::CoreOp *, std::string> * dest = destMap["out"];
       if(dest == NULL) {
	 delete[] newpkt;
       }
       else if(dest->first == NULL) {
	 delete[] newpkt;
       }
       else {
	 assert(newpkt != NULL);
	 lagniappe::RCon * newRCon = new lagniappe::RCon(dest->first, static_cast<lagniappe::RData *>(newpkt), dest->second, pktSize);
	 dest->first->run(newRCon);
       }
     }
     else {
       delete[] newpkt;
     }
   }
   return;
}

void
PhysInterface::start()
{
  Synchronized::Scope mutex(this);
  sysReady = true;
  notifyAll();
  return;
}

void
//PhysInterface::processData(lagniappe::RData * const pkt)
PhysInterface::run(lagniappe::RCon * rcon)
{
  //assert(pkt != NULL);
  assert(rcon->getRData() != NULL);
  while(!fromSet) {
    wait();
  }
  uint32_t sndpktSize = rcon->getSize();
  int r = sendto(coreSocket, (char *)rcon->getRData(), sndpktSize, 0, (sockaddr *)&coreFrom, sizeof(coreFrom));
  if(r < 0) {
    std::cerr << "Problem with sending packet on device " << intfName << std::endl;
    std::cerr << errno << ": " << strerror(errno) << std::endl;
    exit(1);
  }
  char * delPkt = (char *)rcon->getRData();
  delete[] delPkt;
  delete rcon;
  return;
}


#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

void * 
startNIC(void * arg)
{

//  ** OLD TEMPLATE BASED CODE **	
//  MethodWrapper<ProcElement> * mw = static_cast<MethodWrapper<ProcElement> *>(arg);
//  ProcElement* obj = mw->object;
//  void(ProcElement::*mthd)() = mw->method;
//  delete mw;
//  (obj->*mthd)();
//  return NULL;
//  ** OLD TEMPLATE BASED CODE **	

	PhysInterface * nic = static_cast<PhysInterface *>(arg);
	nic->process();
	return NULL;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */
